/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						       summon_obstacle.cpp

	$Header: /game/summon_obstacle.cpp $

	$NoKeywords: $

 ************************************************************************/
// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "summon_obstacle.h"

#include "attackable_obstacle.h"
#include "battlefield.h"
#include "bound_handler.h"
#include "can_cast.h"
#include "combat_creature.h"
#include "combat_object_model_cache.h"
#include "combat_obstacle_placer.h"
#include "combat_spell_registration.h"
#include "compound_object_model.h"
#include "direction.h"
#include "enum_operations.h"
#include "external_string.h"
#include "missile_type.h"
#include "random.h"
#include "spell.h"
#include "spell_actor_animation.h"
#include "stationary_combat_object.h"

static t_combat_spell_registration<t_summon_obstacle> const
	   k_register_summon_obstacle( k_spell_rock_growth );

// -------------------------------------------------------------------
// spell to summon an obstacle
// -------------------------------------------------------------------
t_summon_obstacle::t_summon_obstacle( t_battlefield& battlefield, t_spell spell)
				 : t_combat_spell_cell_target(  battlefield, spell )
{
}

// -------------------------------------------------------------------
// spell to summon an obstacle
// -------------------------------------------------------------------
static t_external_string const k_text_too_close( "too_close.spells" );

bool t_summon_obstacle::can_cast_here( t_map_point_2d const& square, 
		                               std::string* help_text ) const
{
	if (!m_battlefield.can_place( square, get_footprint_size() ))
		return false;

	// check if any enemies are nearby
	t_combat_creature_list::const_iterator index;
	t_combat_creature_list::const_iterator end;
	int								       distance;

	index = m_battlefield.creatures_begin();
	end = m_battlefield.creatures_end();
	for (; index != end; index++)
	{
		if (index->get()->get_number() == 0)
			continue;
		if (m_caster->is_friendly( **index ))
			continue;
		distance = index->get()->get_edge_distance( square );
		if (distance < 5)
			break;
	}
	if (index == end)
		return true;
	if (help_text != 0)
	{
		*help_text = replace_spell_keywords( k_text_too_close, *m_caster, m_spell, 
			                                 index->get() );
	}
	return false;
}

double t_summon_obstacle::get_cancel_weight( t_combat_creature const& target ) const
{
	return 0.0;
}

// --------------------------------------------------------------------
// get terrain type near a point
// --------------------------------------------------------------------
static t_terrain_type get_nearby_terrain( t_battlefield& battlefield,
										  t_map_point_2d        origin,
										  int                   size )
{
	int				total = 0;
	int				limit;
	int				count[ k_basic_terrain_count ];
	t_map_point_2d  point;
	t_map_point_2d  end;
	t_terrain_type  terrain;

	memset( count, 0, sizeof( count ));
	while (total == 0)
	{
		point.row = origin.row;
		end.row = point.row + size;
		limit	= battlefield.get_size();
		if (end.row > limit)
			end.row = limit;
		for (; point.row < end.row; point.row++)
		{
			point.column = origin.column;
			end.column   = point.column + size;
			limit = battlefield.get_row_end( point.row );
			if (end.column > limit)
				end.column = limit;
			for (; point.column < end.column; point.column++)
			{
				terrain = battlefield.get_cell( point ).get_terrain_type();
				if (terrain >= k_basic_terrain_count)
					continue;
				count[terrain]++;
				total++;
			}
		}
		origin.row--;
		origin.column--;
		size += 2;
	}

	t_terrain_type best_terrain = k_terrain_grass;

	for ( terrain = k_terrain_grass; terrain < k_basic_terrain_count; enum_incr( terrain ))
	{
		if (count[terrain] > count[best_terrain])
			best_terrain = terrain;
	}
	return best_terrain;
}

// -------------------------------------------------------------------
// spell to summon an obstacle
// -------------------------------------------------------------------
void t_summon_obstacle::execute( t_combat_creature& caster )
{
	t_obstacle_data_array const& obstacle_data = get_obstacle_data();
	int							 i;
	t_obstacle_weight_list		 weight_list;
	t_obstacle_weight			 weight;
	t_terrain_type				 terrain;
	t_obstacle_type				 type;
	int							 total = 0;
	t_map_point_2d               origin = m_target_square - t_map_point_2d(1,1);
	t_combat_action_message      message( *m_caster, get_action_text( false ) );

	terrain = get_nearby_terrain( m_battlefield, origin, get_footprint_size() );
	for (i = 0; i < obstacle_data.size(); i++)
	{
		weight.obstacle = &obstacle_data[i];
		if (!weight.obstacle->obstacle.can_be_summoned())
			continue;
		type = weight.obstacle->obstacle.get_type();
		weight.weight = get_weighting( type ).terrain[terrain];
		if (weight.weight <= 0)
			continue;
		total += weight.weight;
		weight_list.push_back( weight );
	}
	if (total == 0)
		return;

	int						         choice = random( total );
	t_obstacle_weight_list::iterator index = weight_list.begin();

	for (; index != weight_list.end(); index++)
	{
		weight = *index;
		choice -= weight.weight;
		if (choice <= 0)
			break;
	}

	std::string					name = weight.obstacle->name;
	double						scale = m_battlefield.get_model_scale();
	t_combat_object_model_cache cache = get_combat_object_model_cache( name, scale );
	t_attackable_obstacle*      obstacle;
	int							power = m_caster->get_spell_power( m_spell );

	obstacle = new t_attackable_obstacle( cache, power );
	obstacle->place( m_battlefield, origin << k_battlefield_subcell_shift );
	obstacle->fade_in( m_battlefield, 15, message );
	pay_cost();
	play_sound();
}

// --------------------------------------------------------------------
// Generate the list of casting actions the given caster could take
// for this spell
// --------------------------------------------------------------------
t_combat_ai_action_list t_summon_obstacle::generate_combat_ai_action_list(t_combat_ai &owner)
{
	// Not implemented
	return t_combat_ai_action_list();
}

// -------------------------------------------------------------------
// spell to summon an obstacle
// -------------------------------------------------------------------
int t_summon_obstacle::get_footprint_size() const
{
	return 3;
}

// -------------------------------------------------------------------
// smoke cloud
// -------------------------------------------------------------------
namespace
{
	class t_summon_smoke : public t_combat_spell_cell_target
	{
	public:
		t_summon_smoke( t_battlefield& battlefield, t_spell spell );

		virtual void            execute( t_combat_creature& caster );
		virtual t_combat_ai_action_list generate_combat_ai_action_list(t_combat_ai &owner);
		virtual double					get_cancel_weight( t_combat_creature const& target ) const;
	protected:
		virtual bool can_cast_here( t_map_point_2d const& square, 
									std::string* help_text ) const;
		virtual int  get_footprint_size() const;
		void		 impact( t_combat_creature_ptr caster, t_map_point_2d square );
		void		 place_smoke();

		t_combat_object_model_cache m_cache;
		t_compound_object_model_ptr m_model;
	};
};

static t_combat_spell_registration<t_summon_smoke> const
	   k_register_summon_smoke( k_spell_smoke );

// -------------------------------------------------------------------
// smoke cloud
// -------------------------------------------------------------------
t_summon_smoke::t_summon_smoke( t_battlefield& battlefield, t_spell spell )
              : t_combat_spell_cell_target( battlefield, spell )
{
	m_cache = get_combat_object_model_cache( "spells.smoke", 
		                               m_battlefield.get_model_scale() );
	m_model = m_cache.get();
}


double t_summon_smoke::get_cancel_weight( t_combat_creature const& target ) const
{
	return 0.0;
}

// -------------------------------------------------------------------
// smoke cloud
// -------------------------------------------------------------------
void t_summon_smoke::execute( t_combat_creature& caster )
{
	t_map_point_3d					 target_center;
	t_missile_handler				 handler;
	t_map_point_3d					 source_position;
	t_map_point_2d					 source_cell;
	t_combat_action_message			 message( caster, get_action_text( false ) );

	source_position = caster.get_spell_origin();
	source_cell = source_position >> k_battlefield_subcell_shift;
	target_center = m_battlefield.get_cell_center( m_target_square );
	handler = bound_handler( *this, &t_summon_smoke::impact );
	m_battlefield.launch_missile( k_missile_potion, caster, source_position, 
		                          target_center, handler, message );
	pay_cost();
}

// --------------------------------------------------------------------
// Generate the list of casting actions the given caster could take
// for this spell
// --------------------------------------------------------------------
t_combat_ai_action_list t_summon_smoke::generate_combat_ai_action_list(t_combat_ai &owner)
{
	// Not implemented
	return t_combat_ai_action_list();
}

// -------------------------------------------------------------------
// smoke cloud
// -------------------------------------------------------------------
bool t_summon_smoke::can_cast_here( t_map_point_2d const& square, 
									std::string* help_text ) const
{
	return !m_battlefield.off_map( get_footprint_size(), square );
}

// -------------------------------------------------------------------
// smoke cloud
// -------------------------------------------------------------------
int t_summon_smoke::get_footprint_size() const
{
	return m_model->get_footprint_size().row;
}

// -------------------------------------------------------------------
// smoke cloud
// -------------------------------------------------------------------
void t_summon_smoke::impact( t_combat_creature_ptr caster, t_map_point_2d square )
{
	t_combat_actor_ptr		 cloud;
	t_combat_actor_model_ptr model;
	t_map_point_3d			 target_point;
	t_battlefield&			 battlefield = m_battlefield;
	t_handler				 handler;
	static t_combat_actor_model_cache_set	k_model( "spells.smoke" );
	t_map_point_2d							origin;
	int										footprint_size = get_footprint_size();
	int										half_footprint = (footprint_size - 1) / 2;
	t_combat_action_message					message( *caster, get_action_text( false ) );
	t_spell_actor_animation_ptr				animation;

	model = k_model.get( battlefield.get_model_scale() );


	origin = m_target_square;
	origin.row -= half_footprint;
	origin.column -= half_footprint;
	cloud = new t_unsaved_combat_actor( &m_battlefield, model, k_combat_actor_action_cast_spell, 
		                        k_direction_northeast );
	if (battlefield.off_map( *cloud, origin ))
		return;
	handler = bound_handler( *this, &t_summon_smoke::place_smoke );
	battlefield.place_object( cloud, origin << k_battlefield_subcell_shift );

	animation = new t_spell_actor_animation( m_battlefield, *cloud, this, handler );
	animation->display_action_message( message );

	cloud->set_animation( animation );
	play_sound();
}

// -------------------------------------------------------------------
// smoke cloud
// -------------------------------------------------------------------
void t_summon_smoke::place_smoke()
{
	t_map_point_2d              origin;
	int						    footprint_size = get_footprint_size();
	int							half_footprint = (footprint_size - 1) / 2;

	origin = m_target_square;
	origin.row -= half_footprint;
	origin.column -= half_footprint;
	m_battlefield.place_obstacle( m_cache, origin << k_battlefield_subcell_shift );
}
